﻿/**************************************************************
 *
 * 唯一标识：8368563c-dab0-4ea3-b87a-9f75a09d5888
 * 命名空间：Sgr.Domain.Repositories
 * 创建时间：2023/7/27 10:41:38
 * 机器名称：DESKTOP-S0D075D
 * 创建者：antho
 * 电子邮箱：fengqinhua2016@163.com
 * 描述：
 *
 **************************************************************/

using Sgr.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Sgr.Domain.Entities.Auditing;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Sgr.EntityFrameworkCore;

namespace Sgr.Domain.Repositories
{
    /// <summary>
    /// 基于EF CORE 实现的 IBaseRepository
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <typeparam name="TEntity">实体类型</typeparam>
    /// <typeparam name="TPrimaryKey">主键类型</typeparam>
    public abstract class EfCoreRepositoryOfTEntityAndTPrimaryKey<TDbContext, TEntity, TPrimaryKey>
        : IBaseRepositoryOfTEntityAndTPrimaryKey<TEntity, TPrimaryKey>
        where TEntity : class, IEntity<TPrimaryKey>, IAggregateRoot
        where TDbContext : UnitOfWorkDbContext
    {
        private readonly IDbContextProvider<TDbContext> _dbContextProvider;

        protected readonly IAuditedOperator _auditedOperator;

        protected EfCoreRepositoryOfTEntityAndTPrimaryKey(
            IDbContextProvider<TDbContext> dbContextProvider,
            IAuditedOperator auditedOperator)
        {
            _dbContextProvider = dbContextProvider ?? throw new ArgumentNullException(nameof(dbContextProvider));
            _auditedOperator = auditedOperator ?? throw new ArgumentNullException(nameof(auditedOperator));
        }

        /// <summary>
        /// 检查并设置主键Id
        /// </summary>
        /// <param name="entity">实体</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns></returns>
        protected abstract Task CheckAndSetIdAsync(TEntity entity, CancellationToken cancellationToken = default);

        /// <summary>
        /// 获取实体审计者
        /// </summary>
        /// <returns></returns>
        protected virtual IAuditedOperator? GetAuditedOperator() => _auditedOperator;

        /// <summary>
        /// 获取数据库上下文
        /// </summary>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns></returns>
        protected virtual Task<TDbContext> GetDbContextAsync(CancellationToken cancellationToken = default)
        {
            return _dbContextProvider.GetDbContextAsync(cancellationToken);
        }

        #region 可扩展的方法

        /// <summary>
        /// 更新实体创建相关审计信息
        /// </summary>
        /// <param name="targetObject"></param>
        protected virtual void UpdateCreationAudited(TEntity targetObject)
        {
            this.GetAuditedOperator()?.UpdateCreationAudited(targetObject);
        }

        /// <summary>
        /// 更新实体修改相关审计信息
        /// </summary>
        /// <param name="targetObject"></param>
        protected virtual void UpdateModifyAudited(TEntity targetObject)
        {
            this.GetAuditedOperator()?.UpdateModifyAudited(targetObject);
        }

        /// <summary>
        /// 更新实体删除相关审计信息
        /// </summary>
        /// <param name="targetObject"></param>
        protected virtual void UpdateDeleteAudited(TEntity targetObject)
        {
            this.GetAuditedOperator()?.UpdateDeleteAudited(targetObject);
        }

        /// <summary>
        /// 检查实体是否设置了软删除
        /// </summary>
        /// <returns></returns>
        protected virtual bool CheckEntityIsSoftDelete()
        {
            return typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity));
        }

        /// <summary>
        /// 获取IQueryable
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        protected virtual async Task<IQueryable<TEntity>> GetQueryableAsync(CancellationToken cancellationToken = default)
        {
            var dbContext = await GetDbContextAsync(cancellationToken);

            return dbContext.Set<TEntity>();

            //var query = GetDbContext().Set<TEntity>().AsQueryable();

            //if (CheckEntityIsSoftDelete())
            //{
            //    // 假设软删除实体有一个IsDeleted属性
            //    query = query.Where(e => !((ISoftDelete)e).IsDeleted);
            //}

            //return query;
        }

        /// <summary>
        /// 获取IQueryable
        /// </summary>
        /// <param name="propertiesToInclude"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        protected virtual async Task<IQueryable<TEntity>> GetQueryableAsync(string[] propertiesToInclude, CancellationToken cancellationToken = default)
        {
            IQueryable<TEntity> queryable = await GetQueryableAsync(cancellationToken);

            if (propertiesToInclude != null)
            {
                foreach (var property in propertiesToInclude)
                {
                    queryable = queryable.Include(property);
                }
            }

            return queryable;
        }

        /// <summary>
        /// 判断entity是否已经Attached
        /// </summary>
        /// <param name="dbContext"></param>
        /// <param name="entity"></param>
        /// <returns></returns>
        protected virtual bool IsAttached(DbContext dbContext, TEntity entity)
        {
            TEntity? localEntity = dbContext.Set<TEntity>().Local.Where(t => t.Id!.Equals(entity.Id)).FirstOrDefault();
            if (localEntity != null)
            {
                if (dbContext.Entry(localEntity).State != EntityState.Detached)
                    return true;
            }
            return false;
        }

        #endregion 可扩展的方法

        #region IBaseRepositoryOfTEntityAndTPrimaryKey

        #region INSERT

        /// <summary>
        /// 创建一条记录
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<TEntity> InsertAsync(TEntity entity, CancellationToken cancellationToken = default)
        {
            Check.NotNull(entity, nameof(entity));

            await this.CheckAndSetIdAsync(entity, cancellationToken);
            this.UpdateCreationAudited(entity);

            var dbContext = await GetDbContextAsync(cancellationToken);

            return (await dbContext.Set<TEntity>().AddAsync(entity, cancellationToken)).Entity;
        }

        /// <summary>
        /// 创建多条记录
        /// </summary>
        /// <param name="entities"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task BulkInsertAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken = default)
        {
            Check.NotNull(entities, nameof(entities));

            if (!entities.Any())
                return;

            var dbContext = await GetDbContextAsync(cancellationToken);
            var dbSet = dbContext.Set<TEntity>();

            foreach (var entity in entities)
            {
                await this.CheckAndSetIdAsync(entity, cancellationToken);
                this.UpdateCreationAudited(entity);
            }

            await dbSet.AddRangeAsync(entities, cancellationToken);
        }

        #endregion INSERT

        #region UPDATE

        /// <summary>
        /// 更新一条记录
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<TEntity> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
        {
            Check.NotNull(entity, nameof(entity));

            var dbContext = await GetDbContextAsync(cancellationToken);
            dbContext.Entry(entity).State = EntityState.Modified;

            await this.CheckAndSetIdAsync(entity, cancellationToken);
            this.UpdateModifyAudited(entity);

            return entity;
        }

        /// <summary>
        /// 更新多条记录
        /// </summary>
        /// <param name="entities"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task BulkUpdateAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken = default)
        {
            Check.NotNull(entities, nameof(entities));
            if (!entities.Any())
                return;

            var dbContext = await GetDbContextAsync(cancellationToken);

            // 使用更高效的批处理方式
            dbContext.UpdateRange(entities);

            foreach (var entity in entities)
            {
                await this.CheckAndSetIdAsync(entity, cancellationToken);
                this.UpdateModifyAudited(entity);
            }
        }

        #endregion UPDATE

        #region DELETE

        /// <summary>
        /// 删除一条记录
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default)
        {
            Check.NotNull(entity, nameof(entity));

            if (CheckEntityIsSoftDelete())
            {
                this.UpdateDeleteAudited(entity);
                await UpdateAsync(entity, cancellationToken);
            }
            else
            {
                var dbContext = await GetDbContextAsync(cancellationToken);
                dbContext.Set<TEntity>().Remove(entity);
            }
        }

        /// <summary>
        /// 删除一条记录
        /// </summary>
        /// <param name="id"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task DeleteAsync(TPrimaryKey id, CancellationToken cancellationToken = default)
        {
            var entity = await GetAsync(id, cancellationToken);
            if (entity != null)
                await DeleteAsync(entity, cancellationToken);
        }

        /// <summary>
        /// 删除多条记录
        /// </summary>
        /// <param name="entities"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task BulkDeleteAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken = default)
        {
            Check.NotNull(entities, nameof(entities));
            if (!entities.Any())
                return;

            bool isSoftDelete = CheckEntityIsSoftDelete();

            var dbContext = await GetDbContextAsync(cancellationToken);

            if (isSoftDelete)
            {
                foreach (var entity in entities)
                {
                    this.UpdateDeleteAudited(entity);
                    dbContext.Entry(entity).State = EntityState.Modified;
                }
            }
            else
            {
                dbContext.Set<TEntity>().RemoveRange(entities);
            }
        }

        /// <summary>
        /// 删除多条记录
        /// </summary>
        /// <param name="ids"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task BulkDeleteAsync(IEnumerable<TPrimaryKey> ids, CancellationToken cancellationToken = default)
        {
            var entities = await GetByIdsAsync(ids, cancellationToken);
            await BulkDeleteAsync(entities, cancellationToken);
        }

        #endregion DELETE

        #region IReadOnlyBaseRepositoryOfTEntityAndTPrimaryKey

        /// <summary>
        /// 查询总数
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<long> CountAsync(CancellationToken cancellationToken = default)
        {
            var dbContext = await GetDbContextAsync(cancellationToken);
            return await dbContext.Set<TEntity>().LongCountAsync(cancellationToken);
        }

        /// <summary>
        /// 根据对象全局唯一标识判断实体是否存在
        /// </summary>
        /// <param name="id"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<bool> ExistAsync(TPrimaryKey id,
            CancellationToken cancellationToken = default)
        {
            var dbContext = await GetDbContextAsync(cancellationToken);
            return await dbContext!.Set<TEntity>().AnyAsync(f => f.Id!.Equals(id), cancellationToken);
        }

        /// <summary>
        /// 根据对象全局唯一标识获取实体
        /// </summary>
        /// <param name="id"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<TEntity?> GetAsync(TPrimaryKey id,
            CancellationToken cancellationToken = default)
        {
            var dbContext = await GetDbContextAsync(cancellationToken);
            return await dbContext!.Set<TEntity>().FindAsync(keyValues: new object?[] { id }, cancellationToken: cancellationToken);
        }

        /// <summary>
        /// 根据对象全局唯一标识集合获取实体集合
        /// </summary>
        /// <param name="ids"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<TEntity>> GetByIdsAsync(IEnumerable<TPrimaryKey> ids,
            CancellationToken cancellationToken = default)
        {
            var query = await GetQueryableAsync(cancellationToken);
            return await query
                .Where(f => ids.Contains(f.Id))
                .OrderBy(f => f.Id)
                .ToArrayAsync(cancellationToken);
        }

        /// <summary>
        /// 获取所有实体的集合
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<TEntity>> GetAllAsync(
            CancellationToken cancellationToken = default)
        {
            var query = await GetQueryableAsync(cancellationToken);
            return await query
                .OrderBy(f => f.Id)
                .ToArrayAsync(cancellationToken);
        }

        /// <summary>
        /// 根据对象全局唯一标识获取实体
        /// </summary>
        /// <param name="id"></param>
        /// <param name="propertiesToInclude"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<TEntity?> GetAsync(TPrimaryKey id,
            string[] propertiesToInclude,
            CancellationToken cancellationToken = default)
        {
            var query = await GetQueryableAsync(propertiesToInclude, cancellationToken);
            return await query.FirstOrDefaultAsync(f => f.Id!.Equals(id), cancellationToken);
        }

        /// <summary>
        /// 根据对象全局唯一标识集合获取实体集合
        /// </summary>
        /// <param name="ids"></param>
        /// <param name="propertiesToInclude"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<TEntity>> GetByIdsAsync(IEnumerable<TPrimaryKey> ids,
            string[] propertiesToInclude,
            CancellationToken cancellationToken = default)
        {
            var query = await GetQueryableAsync(propertiesToInclude, cancellationToken);
            return await query
                 .Where(f => ids.Contains(f.Id))
                 .OrderBy(f => f.Id)
                 .ToArrayAsync(cancellationToken);
        }

        /// <summary>
        /// 获取所有实体的集合
        /// </summary>
        /// <param name="propertiesToInclude"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<TEntity>> GetAllAsync(
            string[] propertiesToInclude,
            CancellationToken cancellationToken = default)
        {
            var query = await GetQueryableAsync(propertiesToInclude, cancellationToken);
            return await query
                .OrderBy(f => f.Id)
                .ToArrayAsync(cancellationToken);
        }

        #region related-data/explicit

        /// <summary>
        ///
        /// </summary>
        /// <typeparam name="TProperty"></typeparam>
        /// <param name="entity"></param>
        /// <param name="propertyExpression"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task CollectionAsync<TProperty>(TEntity entity,
            Expression<Func<TEntity, IEnumerable<TProperty>>> propertyExpression,
            CancellationToken cancellationToken = default) where TProperty : class
        {
            Check.NotNull(entity, nameof(entity));
            Check.NotNull(propertyExpression, nameof(propertyExpression));

            var dbContext = await GetDbContextAsync(cancellationToken);
            EntityEntry<TEntity> entityEntry = GetExplicitEntityEntry(entity, dbContext);
            await entityEntry.Collection(propertyExpression).LoadAsync(cancellationToken);
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="propertyName"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task CollectionAsync(TEntity entity,
            string propertyName,
            CancellationToken cancellationToken = default)
        {
            var dbContext = await GetDbContextAsync(cancellationToken);
            var navigation = dbContext.Entry(entity).Metadata.FindNavigation(propertyName);

            if (navigation != null)
            {
                EntityEntry<TEntity> entityEntry = GetExplicitEntityEntry(entity, dbContext);
                await entityEntry.Collection(navigation).LoadAsync(cancellationToken);
            }
        }

        /// <summary>
        ///
        /// </summary>
        /// <typeparam name="TProperty"></typeparam>
        /// <param name="entity"></param>
        /// <param name="propertyExpression"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task ReferenceAsync<TProperty>(TEntity entity,
            Expression<Func<TEntity, TProperty?>> propertyExpression,
            CancellationToken cancellationToken = default) where TProperty : class
        {
            var dbContext = await GetDbContextAsync(cancellationToken);
            EntityEntry<TEntity> entityEntry = GetExplicitEntityEntry(entity, dbContext);
            await entityEntry.Reference(propertyExpression).LoadAsync(cancellationToken);
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="propertyName"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task ReferenceAsync(TEntity entity,
            string propertyName,
            CancellationToken cancellationToken = default)
        {
            var dbContext = await GetDbContextAsync(cancellationToken);
            var navigation = dbContext.Entry(entity).Metadata.FindNavigation(propertyName);
            if (navigation != null)
            {
                EntityEntry<TEntity> entityEntry = GetExplicitEntityEntry(entity, dbContext);
                await entityEntry.Reference(navigation).LoadAsync(cancellationToken);
            }
        }

        protected virtual EntityEntry<TEntity> GetExplicitEntityEntry(TEntity entity, DbContext dbContext)
        {
            var entityEntry = dbContext.Entry(entity);
            if (entityEntry.State == EntityState.Detached)
                entityEntry.State = EntityState.Unchanged;
            return entityEntry;
        }

        #endregion related-data/explicit

        #endregion IReadOnlyBaseRepositoryOfTEntityAndTPrimaryKey

        #endregion IBaseRepositoryOfTEntityAndTPrimaryKey
    }

    //public abstract class EfCoreRepositoryOfTEntityAndTPrimaryKey<TModule, TEntity, TPrimaryKey>
    //: EfCoreRepositoryOfTEntityAndTPrimaryKey<UnitOfWorkDbContext<TModule>, TEntity, TPrimaryKey>
    //where TEntity : class, IEntity<TPrimaryKey>, IAggregateRoot
    //where TModule : class
    //{
    //    protected EfCoreRepositoryOfTEntityAndTPrimaryKey(
    //        UnitOfWorkDbContext<TModule> dbContext,
    //        IAuditedOperator auditedOperator)
    //        : base(dbContext, auditedOperator)
    //    {
    //    }

    //    // 可以添加特定于模块的仓储功能
    //}
}